/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package xmlparser;
//import org.xml.

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import org.jdom2.input.sax.XMLReaders;
import org.jdom2.output.XMLOutputter;
import syntax.Clock;
import syntax.ClockConditionAtom;
import syntax.ClockGuard;
import syntax.Domain;
import syntax.Location;
import syntax.Message;
import syntax.Operator;
import syntax.Repository;
import syntax.TIOA;
import syntax.Transition;

/**
 *
 * @author ennouaary
 */
public class XMLParser {

    File xmlFile = null;
    final static String SYSTEM_DEFINITION = "system_definition";
    final static String SYSTEM_DEFINITION_XMLNAMESPACE = "xmlns";
    final static String SYSTEM_DEFINITION_XMLNAMESPACE_PREFIX = "xsi";
    final static String SYSTEM_DEFINITION_XMLNAMESPACE_VALUE = "http://www.w3.org/2001/XMLSchema-instance";
    final static String SYSTEM_DEFINITION_XSI_NAMESPACE_PREFIX = "xsi";
    final static String SYSTEM_DEFINITION_XSI_NAMESPACE_VALUE = "noNamespaceSchemaLocation";
    final static String SYSTEM_DEFINITION_XSI_VALUE = "system.xsd";
    final static String SYSTEM = "system";
    final static String SYSTEM_NAME = "name";
    final static String COMPONENT_NAME = "name";
    final static String CLOCKS = "clocks";
    final static String CLOCK = "clock";
    final static String CLOCK_NAME = "name";
    final static String CLOCK_MINVALUE = "min_value";
    final static String CLOCK_MAXVALUE = "max_value";
    final static String LOCATIONS = "locations";
    final static String LOCATION = "location";
    final static String LOCATION_NAME = "name";
    final static String INITIAL_LOCATION = "initial_location";
    final static String MESSAGES = "messages";
    final static String MESSAGE = "message";
    final static String MESSAGE_NAME = "name";
    final static String MESSAGE_TYPE = "type";
    final static String MESSAGE_TYPE_INPUT = "INPUT";
    final static String MESSAGE_TYPE_OUTPUT = "OUTPUT";
    final static String TRANSITIONS = "transitions";
    final static String TRANSITION = "transition";
    final static String TRANSITION_NAME = "name";
    final static String TRANSITION_SOURCE = "source_location";
    final static String TRANSITION_DESTINATION = "destination_location";
    final static String TRANSITION_MESSAGE = "message";
    final static String TRANSITION_CLOCKGUARD = "clock_guard";
    final static String TRANSITION_CLOCKGUARD_CONDITIONATOM = "clock_guard";
    final static String TRANSITION_CLOCKGUARD_CONDITIONATOM_CLOCK = "clock";
    final static String TRANSITION_CLOCKGUARD_CONDITIONATOM_LOWERBOUND = "lower_bound";
    final static String TRANSITION_CLOCKGUARD_CONDITIONATOM_LOWEROPERATOR = "lower_operator";
    final static String TRANSITION_CLOCKGUARD_CONDITIONATOM_UPPEROPERATOR = "upper_operator";
    final static String TRANSITION_CLOCKGUARD_CONDITIONATOM_UPPERBOUND = "upper_bound";
    final static String TRANSITION_RESETCLOCKS = "reset_clocks";
    final static String TRANSITION_RESETCLOCK = "reset_clock";

    Location getLocation(String name, Repository repository) throws XMLParsingException {
        Location location = repository.getLocation(name);
        if (location == null) {
            //TODO define and raise an exception
            throw new XMLParsingException("Location " + name + " Not Declared");
        }
        return location;
    }

    Clock getClock(String name, Repository repository) throws XMLParsingException {
        Clock clock = repository.getClock(name);
        if (clock == null) {
            //TODO define and raise an exception
            throw new XMLParsingException("Clock " + name + " Not Declared");
        }
        return clock;
    }

    Message getMessage(String name, Repository repository) throws XMLParsingException {
        Message message = repository.getMessage(name);
        if (message == null) {
            //TODO define and raise an exception
            throw new XMLParsingException("Message " + name + " Not Declared");
        }
        return message;
    }

    Location addLocation(String name, Repository repository) throws XMLParsingException {
        Location location = repository.addLocation(name);
        if (location == null) {
            //TODO define and raise an exception
            throw new XMLParsingException("Location " + name + " already declared");
        }
        return location;
    }

    Clock addClock(String name, Float l_b, Float u_b, Repository repository) throws XMLParsingException {
        Clock clock = repository.addClock(name, l_b, u_b);
        if (clock == null) {
            //TODO define and raise an exception
            throw new XMLParsingException("Clock " + name + " already Declared");
        }
        return clock;
    }

    Message addMessage(String name, String messageType, Repository repository) throws XMLParsingException {
        Message msg = null;
        if (messageType.compareTo(MESSAGE_TYPE_INPUT) == 0) {
            msg = repository.addInput(name);
        } else {
            msg = repository.addOutput(name);
        }
        if (msg == null) {
            //TODO define and raise an exception
            throw new XMLParsingException("Message " + name + " already Declared");
        }
        return msg;
    }

    Transition addTransition(String name, Location src, Location dest, Message message,
            Collection<Clock> resetClocks, ClockGuard clockGuard, Repository repository) throws XMLParsingException {
        Transition transition = repository.addTransition(name, src, dest, message, resetClocks, clockGuard);
        if (transition == null) {
            //TODO define and raise an exception
            throw new XMLParsingException("Transition " + name + " already Declared");
        }
        return transition;
    }

    Operator.Op parseOperator(String xmlValue) {
        if (xmlValue.compareTo("LT") == 0) {
            return Operator.Op.getLT();
        } else if (xmlValue.compareTo("LE") == 0) {
            return Operator.Op.getLE();
        } else if (xmlValue.compareTo("EQ") == 0) {
            return Operator.Op.getEQ();
        } else if (xmlValue.compareTo("GE") == 0) {
            return Operator.Op.getGE();
        } else {
            return Operator.Op.getGT();
        }
    }

    String parseOperator(Operator.Op op) {
        String res = "";
        if (op.equals(op.EQ)) {
            return "EQ";
        }
        if (op.equals(op.LE)) {
            return "LE";
        }
        if (op.equals(op.LT)) {
            return "LT";
        }
        return res;
    }

    String parseMessageType(Message message) {
        if (message.isInput()) {
            return MESSAGE_TYPE_INPUT;
        }
        if (message.isOutput()) {
            return MESSAGE_TYPE_OUTPUT;
        }
        return null;
    }

    public TIOA parse() throws XMLParsingException {
        SAXBuilder builder = new SAXBuilder(XMLReaders.XSDVALIDATING);
        TIOA tioa = null;

        try {
            Document document = (Document) builder.build(xmlFile);
            Element rootNode = document.getRootElement();
            rootNode = rootNode.getChild(SYSTEM);
            String systemName = rootNode.getAttributeValue(SYSTEM_NAME);

            List<Element> list = rootNode.getChildren();

            //Iterating over system components
            for (Element element : list) {
                String compName = element.getAttributeValue(COMPONENT_NAME);

                tioa = new TIOA(compName);
                Repository repository = tioa.getRepository();

                //Iterating over clocks definition
                Element clocksElement = element.getChild(CLOCKS);
                List<Element> listClocks = clocksElement.getChildren();
                for (Element clockElement : listClocks) {
                    String clockName = clockElement.getChildTextTrim(CLOCK_NAME);
                    String minValue = clockElement.getChildTextTrim(CLOCK_MINVALUE);
                    String maxValue = clockElement.getChildTextTrim(CLOCK_MAXVALUE);
                    Clock clock = addClock(clockName, Float.parseFloat(minValue), Float.parseFloat(maxValue), repository);
                }

                //Iterating over locations definition
                Element locationsElement = element.getChild(LOCATIONS);
                List<Element> listLocations = locationsElement.getChildren();
                for (Element locationElement : listLocations) {
                    String locationName = locationElement.getChildTextTrim(LOCATION_NAME);
                    Location location = addLocation(locationName, repository);
                }

                // getting initial location
                String initialLocationName = element.getChildTextTrim(INITIAL_LOCATION);
                Location initialLocation = getLocation(initialLocationName, repository);
                tioa.setInitialLocation(initialLocation);


                //getting messages
                Element messagesElement = element.getChild(MESSAGES);
                List<Element> listMessages = messagesElement.getChildren();
                for (Element messageElement : listMessages) {
                    String messageName = messageElement.getChildTextTrim(MESSAGE_NAME);
                    String messageType = messageElement.getChildTextTrim(MESSAGE_TYPE);
                    Message message = addMessage(messageName, messageType, repository);
                }

                //getting transitions
                Element transitionsElement = element.getChild(TRANSITIONS);
                List<Element> listTransitions = transitionsElement.getChildren();
                for (Element transitionElement : listTransitions) {
                    String transitionName = transitionElement.getChildTextTrim(TRANSITION_NAME);
                    String transitionSource = transitionElement.getChildTextTrim(TRANSITION_SOURCE);
                    String transitionDestination = transitionElement.getChildTextTrim(TRANSITION_DESTINATION);
                    String transitionMessage = transitionElement.getChildTextTrim(TRANSITION_MESSAGE);

                    Location srcLocation, destLocation;
                    Message message;

                    //Getting source & dest location
                    srcLocation = getLocation(transitionSource, repository);
                    destLocation = getLocation(transitionDestination, repository);

                    //getting message
                    message = getMessage(transitionMessage, repository);

                    //getting clock guard
                    Element clockGuardElement = transitionElement.getChild(TRANSITION_CLOCKGUARD);
                    ClockGuard clockGuard = new ClockGuard();
                    List<Element> listClockConditionAtoms = clockGuardElement.getChildren();
                    for (Element clockConditionElement : listClockConditionAtoms) {
                        String conditionAtomClockName = clockConditionElement.getChildTextTrim(TRANSITION_CLOCKGUARD_CONDITIONATOM_CLOCK);
                        String l_bString = clockConditionElement.getChildTextTrim(TRANSITION_CLOCKGUARD_CONDITIONATOM_LOWERBOUND);
                        String u_bString = clockConditionElement.getChildTextTrim(TRANSITION_CLOCKGUARD_CONDITIONATOM_UPPERBOUND);
                        String l_opString = clockConditionElement.getChildTextTrim(TRANSITION_CLOCKGUARD_CONDITIONATOM_LOWEROPERATOR);
                        String u_opString = clockConditionElement.getChildTextTrim(TRANSITION_CLOCKGUARD_CONDITIONATOM_UPPEROPERATOR);
                        Clock clock = getClock(conditionAtomClockName, repository);
                        Float l_b = Float.parseFloat(l_bString);
                        Float u_b = Float.parseFloat(u_bString);
                        Operator.Op l_op, u_op;
                        l_op = parseOperator(l_opString);
                        u_op = parseOperator(u_opString);
                        ClockConditionAtom clockConditionAtom = new ClockConditionAtom(l_b, u_b, clock, l_op, u_op);
                        clockGuard.addClockConditionAtom(clockConditionAtom);
                    }

                    //getting reset clocks
                    Element resetClocksElement = transitionElement.getChild(TRANSITION_RESETCLOCKS);
                    Collection<Clock> resetClocks = new LinkedList();
                    List<Element> listResetClocks = resetClocksElement.getChildren();
                    for (Element resetClockElement : listResetClocks) {
                        String resetClockName = resetClockElement.getTextTrim();
                        Clock clock = getClock(resetClockName, repository);
                        resetClocks.add(clock);
                    }

                    Transition transition = addTransition(transitionName, srcLocation, destLocation,
                            message, resetClocks, clockGuard, repository);
                }


            }


        } catch (IOException io) {
            System.out.println(io.getMessage());
        } catch (JDOMException jdomex) {
            System.out.println(jdomex.getMessage());
        }
        return tioa;

    }

    private Element getMessage(Message message) {
        Element messageElement = new Element(MESSAGE);
        Element messageName = getSimpleElement(MESSAGE_NAME, message.getName());
        Element messageType = getSimpleElement(MESSAGE_TYPE, parseMessageType(message));
        messageElement.addContent(messageName);
        messageElement.addContent(messageType);
        return messageElement;
    }

    private Element getMessages(Collection<Message> messages) {
        Element messagesElement = new Element(MESSAGES);
        for (Message message : messages) {
            if (!message.isInternal()) {
                Element messageElement = getMessage(message);
                messagesElement.addContent(messageElement);
            }
        }
        return messagesElement;
    }

    private Element getClock(Clock clock) {
        Element clockElement = new Element(CLOCK);
        Element clockName = getSimpleElement(CLOCK_NAME, clock.getName());
        Element clockLB = getSimpleElement(CLOCK_MINVALUE, Float.toString(clock.getDomain().getL_b()));
        Element clockUB = getSimpleElement(CLOCK_MAXVALUE, Float.toString(clock.getDomain().getU_b()));
        clockElement.addContent(clockName);
        clockElement.addContent(clockLB);
        clockElement.addContent(clockUB);
        return clockElement;
    }

    private Element getClocks(Collection<Clock> clocks) {
        Element clocksElement = new Element(CLOCKS);
        for (Clock clock : clocks) {
            Element clockElement = getClock(clock);
            clocksElement.addContent(clockElement);
        }
        return clocksElement;
    }

    private Element getLocation(Location location) {
        Element locationElement = new Element(LOCATION);
        Element locationName = getSimpleElement(LOCATION_NAME, location.getName());
        locationElement.addContent(locationName);
        return locationElement;
    }

    private Element getLocations(Collection<Location> locations) {
        Element locationsElement = new Element(CLOCKS);
        for (Location location : locations) {
            Element locationElement = getLocation(location);
            locationsElement.addContent(locationElement);
        }
        return locationsElement;
    }

    private Element getSimpleElement(String name, String value) {
        Element element = new Element(name);
        element.addContent(value);
        return element;
    }

    private Element getClockConditionAtom(ClockConditionAtom clockConditionAtom) {
        Element clockConditionAtomElement = new Element(TRANSITION_CLOCKGUARD_CONDITIONATOM);
        Element clockConditionAtomClock = getSimpleElement(TRANSITION_CLOCKGUARD_CONDITIONATOM_CLOCK,
                clockConditionAtom.getClock().getName());
        clockConditionAtomElement.addContent(clockConditionAtomClock);
        Element clockConditionAtomLB = getSimpleElement(TRANSITION_CLOCKGUARD_CONDITIONATOM_LOWERBOUND,
                Float.toString(clockConditionAtom.getDomain().getL_b()));
        clockConditionAtomElement.addContent(clockConditionAtomLB);

        Element clockConditionAtomLOP = getSimpleElement(TRANSITION_CLOCKGUARD_CONDITIONATOM_LOWEROPERATOR,
                parseOperator(clockConditionAtom.getLowerRelation()));
        clockConditionAtomElement.addContent(clockConditionAtomLOP);

        Element clockConditionAtomUOP = getSimpleElement(TRANSITION_CLOCKGUARD_CONDITIONATOM_UPPEROPERATOR,
                parseOperator(clockConditionAtom.getUpperRelation()));
        clockConditionAtomElement.addContent(clockConditionAtomUOP);

        Element clockConditionAtomUB = getSimpleElement(TRANSITION_CLOCKGUARD_CONDITIONATOM_UPPERBOUND,
                Float.toString(clockConditionAtom.getDomain().getU_b()));
        clockConditionAtomElement.addContent(clockConditionAtomUB);

        return clockConditionAtomElement;
    }

    private Element getClockGuard(ClockGuard clockGuard) {
        Element clockGuardElement = new Element(TRANSITION_CLOCKGUARD);
        for (ClockConditionAtom clockConditionAtom : clockGuard.getConditionAtom()) {
            Element clockConditionAtomElement = getClockConditionAtom(clockConditionAtom);
            clockGuardElement.addContent(clockConditionAtomElement);
        }
        return clockGuardElement;
    }

    private Element getResetClock(Clock clock) {
        Element clockElement = getSimpleElement(TRANSITION_RESETCLOCK, clock.getName());
        return clockElement;
    }

    private Element getResetClocks(Collection<Clock> clocks) {
        Element clocksElement = new Element(CLOCKS);
        for (Clock clock : clocks) {
            Element clockElement = getResetClock(clock);
            clocksElement.addContent(clockElement);
        }
        return clocksElement;
    }

    private Element getTransition(Transition transition) {
        Element transitionElement = new Element(TRANSITION);
        Element transitionName = getSimpleElement(TRANSITION_NAME,
                transition.getName());
        transitionElement.addContent(transitionName);

        Element transitionSource = getSimpleElement(TRANSITION_SOURCE,
                transition.getSrcLocation().toString());
        transitionElement.addContent(transitionSource);

        Element transitionDestination = getSimpleElement(TRANSITION_DESTINATION,
                transition.getDestLocation().toString());
        transitionElement.addContent(transitionDestination);

        Element transitionMessage = getSimpleElement(TRANSITION_MESSAGE,
                transition.getMessage().toString());
        transitionElement.addContent(transitionMessage);

        Element transitionClockGuard = getClockGuard(transition.getClockGuard());
        transitionElement.addContent(transitionClockGuard);

        Element transitionResetClocks = getResetClocks(transition.getResetClocks());
        transitionElement.addContent(transitionResetClocks);

        return transitionElement;
    }

    private Element getTransitions(Collection<Transition> transitions) {
        Element transitionsElement = new Element(TRANSITIONS);
        for (Transition transition : transitions) {
            Element transitionElement = getTransition(transition);
            transitionsElement.addContent(transitionElement);
        }
        return transitionsElement;
    }

    private Element getTIOASystem(TIOA tioa) {
        Repository repository = tioa.getRepository();
        Element element = new Element(SYSTEM);
        element.setAttribute(SYSTEM_NAME, tioa.getName());
        Element clocks = getClocks(repository.getAllClocks());
        element.addContent(clocks);
        Element messages = getMessages(repository.getAllMessages());
        element.addContent(messages);
        Element locations = getLocations(repository.getAllLocations());
        element.addContent(locations);
        Element initialLocation = getSimpleElement(INITIAL_LOCATION,
                tioa.getInitialLocation().toString());
        element.addContent(initialLocation);
        Element transitions = getTransitions(repository.getAllTransitions());
        element.addContent(transitions);
        return element;
    }

    public String tioaToXml(TIOA tioa) {
        StringBuilder stb = new StringBuilder();
        XMLOutputter xmlOut = new XMLOutputter();

        Element systemDefinition = new Element(SYSTEM_DEFINITION,
                SYSTEM_DEFINITION_XMLNAMESPACE_PREFIX,
                SYSTEM_DEFINITION_XMLNAMESPACE_VALUE);
//        Namespace ns = Namespace.getNamespace(SYSTEM_DEFINITION_XMLNAMESPACE_PREFIX, SYSTEM_DEFINITION_XMLNAMESPACE_VALUE);
        Namespace ns = systemDefinition.getNamespace("xsi");
        
        systemDefinition.setAttribute(SYSTEM_DEFINITION_XSI_NAMESPACE_VALUE, SYSTEM_DEFINITION_XSI_VALUE, ns);
        Document doc = new Document(systemDefinition);
//        doc.setRootElement(systemDefinition);
        Element systemElement = getTIOASystem(tioa);
        systemDefinition.addContent(systemElement);
        stb.append(xmlOut.outputString(doc));
        return stb.toString();
    }

    public XMLParser(File file) {
        this.xmlFile = file;
    }

    public static void main(String[] args) {

        SAXBuilder builder = new SAXBuilder();
        File xmlFile = new File("C:\\Users\\ennouaary\\Documents\\NetBeansProjects\\TIOA\\examples\\tel.xml");
        XMLParser parser = new XMLParser(xmlFile);
    }
}
